
/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*
*/

Gimme.Effects =
{
	Animation: function(config)
	{
		var _elements = config.elements || [];
		var _duration = config.duration || 1;
		
		var _from = config.from || {};
		var _to = config.to || {};
		var _delta = {};
		(function initToAndFromValues()
		{
			var i, key, elem,
			f = {},
			t = {},
			d = {};
			
			for (key in _from)
			{
				f[key] = [];
				t[key] = [];
				d[key] = [];
				
				i = _elements.length;
				while (i--)
				{
					elem = _elements[i];
					f[key][i] = typeof _from[key] === 'function' ? _from[key](elem, key) : _from[key];
					t[key][i] = typeof _to[key] === 'function' ? _to[key](elem, key) : _to[key];
					d[key][i] = t[key][i] - f[key][i];
				}
			}
			
			_from = f;
			_to = t;
			_delta = d;
		})();
		
		var _animate = config.animate;
		var _easing = config.easing || function(pct)
		{
			return pct;
		};
		
		this.startTime = config.startTime || 0;
		this.duration = _duration;
		
		this.add_element = function(element)
		{
			_elements.push(element);
		};
		
		this.remove_element = function(element)
		{
			var i = _elements.length;
			while (i--)
			{
				if (_elements[i] === element)
				{
					_elements.splice(i, 1);
					break;
				}
			}		
		};
		
		this.tick = tick;
		function tick(t)
		{	
			var ease = _easing(t);
			
			var i = _elements.length;
			while (i--)
			{
				var key, val = {};
				for (key in _delta)
				{
					val[key] = _from[key][i] + _delta[key][i] * ease;
				}
				_animate(_elements[i], val, t);				
			}
		}
	},
	
	Storyboard: function(config)
	{
		var _prevTime;
		var _pct = 0;
		var _dir = 1;
		var _running = false;

		var _animations = config.animations;
		var _duration = config.duration || 0;
		var _completed = config.completed;
		var _aborted = config.aborted;
		
		var raf = Gimme.Effects.Animation.requestAnimationFrame;
		
		function start()
		{			
			_running = true;
			_prevTime = new Date().getTime();
			runner();
		}

		this.begin = begin;
		function begin()
		{
			_pct = 0;
			_dir = 1;
			start();
		}
		
		this.abort = abort;
		function abort()
		{
			pause();
			_aborted && _aborted(_pct);
		}
		
		this.pause = pause;
		function pause()
		{
			_running = false;
			
		};
		
		this.resume = resume;
		function resume()
		{
			start();
		}
		
		this.forward = forward;
		function forward()
		{
			_dir = 1;
			start();
		}
		
		this.backward = backward;
		function backward()
		{
			_dir = -1;
			start();
		}
		
		this.reverse = reverse;
		function reverse()
		{
			_dir *= -1;
			start();
		}
		
		this.seek = seek;
		function seek(pct)
		{
			_pct = pct;
			tick();
		}
		
		this.is_running = isRunning;
		function isRunning()
		{
			return _running;
		}
		
		this.get_direction = getDirection;
		function getDirection()
		{
			return _dir;
		}
		
		this.get_animations = getAnimations;
		function getAnimations()
		{
			return _animations;
		}
		
		this.get_duration = getDuration;
		function getDuration()
		{
			return _duration;
		}
		
		function runner()
		{
			if (_running)
			{
				raf(runner);
				var currentTime = new Date().getTime();
				_pct += (currentTime - _prevTime) / _duration * _dir;
				_prevTime = currentTime;
				_pct = _pct < 0 ? 0 : _pct > 1 ? 1 : _pct;
				
				tick();
				
				if ((_dir === -1 && _pct === 0) || (_dir === 1 && _pct === 1))
				{
					pause();
					_completed && _completed(_pct);
				}
			}
		}
		
		function tick()
		{
			var i = _animations.length;
			while (i--)
			{
				var t = (_duration * _pct - _animations[i].startTime) / _animations[i].duration;
				t = _dir === -1 && t < 0 ? 0 : _dir === 1 && t > 1 ? 1 : t;
				if (t >= 0 && t <= 1)
				{
					_animations[i].tick(t);
				}
			}
		}
	},
	
	Easing:
	{
		Back: new function()
		{
			function easeIn(t, frequency, suppression, amplitude)
			{
				frequency = .5;				// todo: || default value
				suppression = 0;			// todo: || default value
				amplitude = .5;				// todo: || default value

				return Math.pow((1 - t), suppression) * amplitude * Math.sin(2 * Math.PI * t * frequency) * -1 + t;
			}
			this.ease_in = easeIn;
			
			function easeOut(t, frequency, suppression, amplitude)
			{
			}
			this.ease_out = easeOut;
			
			function easeInOut(t, frequency, suppression, amplitude)
			{
			}
			this.ease_in_out = easeInOut;
		},
		
		Bounce: new function()
		{
			function easeIn(t, weightiness, bounces)
			{
				weightiness = weightiness || 1.5;
				bounces = bounces || 4;
				
				return Math.abs(Math.pow(t, weightiness) * Math.cos(Math.PI * t * bounces));
			}
			this.ease_in = easeIn;
			
			function easeOut(t, weightiness, bounces)
			{
				return 1 - Gimme.Effects.Easing.Bounce.ease_in(1 - t, weightiness, bounces);
			}
			this.ease_out = easeOut;
			
			function easeInOut(t, weightiness, bounces)
			{
				var delta = 1;
				
				var adj = 0;
				var ease = Gimme.Effects.Easing.Bounce.ease_in;
				if (t > .5)
				{
					ease = Gimme.Effects.Easing.Bounce.ease_out;
					t -= .5;
					adj = delta / 2;
				}
				
				return ease(t, weightiness, bounces) - adj;
			}
			this.ease_in_out = easeInOut;
		},

		Elastic: new function()
		{
			function easeIn(t, springiness, oscillations)
			{
			}
			this.ease_in = easeIn;
			
			function easeOut(t, springiness, oscillations)
			{
				springiness = springiness || 3.5;
				oscillations = oscillations || 5;
				
				return 1 - (Math.pow(1 - t, springiness) * Math.cos(2 * Math.PI * t * oscillations));
			}
			this.ease_out = easeOut;
			
			function easeInOut(t, springiness, oscillations)
			{
			}
			this.ease_in_out = easeInOut;
		},
		
		Exponential: new function()
		{
			function easeIn(t, exp)
			{
				return Math.pow(t, exp);
			}
			this.ease_in = easeIn;
			
			function easeOut(t, exp)
			{
				return Math.pow(t, 1 / exp);
			}
			this.ease_out = easeOut;
			
			function easeInOut(t, exp)
			{
				return t <= .5 ? easeIn(t * 2, exp) * .5 : (easeOut((t - .5) * 2, exp) * .5) + .5;
			}
			this.ease_in_out = easeInOut;
		}
	},
	
	Enums:
	{
		Directions:
		{
			vertically: 1,
			horizontally: 2,
			both: 3
		},
		
		Speeds:
		{
			quickly: 500,
			slowly: 1000
		}
	},
	
	RunningAnimations: function()
	{
		var _storyboards = {};
		return {
			add: function(guid, sb)
			{
				_storyboards[guid] = sb;
			},
			
			remove: function(guid)
			{
				delete _storyboards[guid];
			},
			
			get: function(guid)
			{
				return _storyboards[guid] || new Gimme.Effects.Storyboard({animations:[]});
			},
			
			contains: function(guid)
			{
				return !!_storyboards[guid];
			}
		};		
	}()
};

// Static function of the Gimme.Effects.Storyboard class
Gimme.Effects.Storyboard.create = function(items, from, to, processFn, duration, guid, callback, easing)
{
	duration = Math.floor(duration) || Gimme.Effects.Enums.Speeds[duration] || Gimme.Effects.Enums.Speeds.quickly;		
	var sb = new Gimme.Effects.Storyboard(
	{			
		duration: duration,
		completed: function()
		{
			callback && callback.call(this);
			Gimme.Effects.RunningAnimations.remove(guid);
			sb = null;
		},
		aborted: function()
		{
			Gimme.Effects.RunningAnimations.remove(guid);
			sb = null;
		},
		animations:
		[
			new Gimme.Effects.Animation(
			{
				from: from,
				to: to,
				duration: duration,
				animate: processFn,
				easing: easing,
				elements: items
			})
		]
	});
	
	if (guid)
	{
		Gimme.Effects.RunningAnimations.add(guid, sb);
	}
	
	return sb;		
};

// Static functions attached to Gimme.Effects.Animation for normalizing the upcoming W3C functions:
// requestAnimationFrame and cancelAnimationFrame (wrapped in a closure to hide: iid and hash from the global space)
(function()
{
	// iid = interval id -- used to track (and clear) a pending requestAnimationFrame call
	var iid = 0;

	// since mozilla doesn't (yet) support clearAnimationFrame, the hash helps us determine what (if any)
	// functions have been queue'd up
	var hash = {};

	Gimme.Effects.Animation.requestAnimationFrame = function()
	{
		var delay = 16;			// roughly 60 fps (1000/60) but setTimeout and RAF aren't that accurate, so 16 is adequate
		var w = window;
		var raf = 
			w.requestAnimationFrame ||
			w.webkitRequestAnimationFrame ||
			w.mozRequestAnimationFrame ||
			w.oRequestAnimationFrame ||
			w.msRequestAnimationFrame;

		function proxy(tick, key)
		{
			return function()
			{
				hash[key] && tick(new Date().getTime());
				delete hash[key];
			};
		}

		function nativeRAF(tick, bounds)
		{
			var p = hash[++iid] = proxy(tick, iid);
			raf(p, bounds);
			return iid;
		}

		function syntheticRAF(tick)
		{
			var p = hash[++iid] = proxy(tick, iid);
			w.setTimeout(p, delay);
			return iid;
		}

		return raf ? nativeRAF : syntheticRAF;
	}();

	Gimme.Effects.Animation.cancelAnimationFrame = function(key)
	{
		delete hash[key];
	};
})();

